package control;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.Properties;
import java.util.regex.Pattern;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import model.loader.GameFactory;
import model.objects.ImageMap;
import model.objects.Level;
import physics.PhysicsManager;
import util.Constants;
import view.Canvas;
import view.Menu;
import view.SplashScreen;

/**
 * Controller for the game engine
 *     Manages and initializes: the JFrame, resource files,
 *     the game loop, levels
 * 
 * @author Mark Govea
 */
public class GameController {

    private JFrame myFrame;
    private Timer myTimer;
    private Properties myProperties;
    private String myGameFolder;
    private String[] myLevelFiles;
    private String[] myForceFiles;
    private ImageMap myImages;
    private Canvas myCanvas;
    private InputManager myInputManager;
    private PhysicsManager myPhysicsManager;
    private Level myLevel;
    private int myLevelNumber;

    /**
     * Initializes and starts the game engine
     * 
     * @param properties Properties file used to specify all game resources
     */
    public GameController (Properties properties) {
        // save properties
        myProperties = properties;
        myGameFolder = myProperties.getProperty(
            Constants.GameFileFields.RESOURCE_FOLDER);

        // generate splash screen before resource loading
        initFrameAndSplash();

        // load resources
        myImages = GameFactory.loadImages(new File(myGameFolder +
            myProperties.getProperty(Constants.GameFileFields.IMAGE_FILE)));

        myLevelFiles = parseArray(myGameFolder +
            myProperties.getProperty(Constants.GameFileFields.LEVEL_FILES));
        for (String e : myLevelFiles) {
            e = myGameFolder + e;
        }

        myForceFiles = parseArray(myGameFolder +
                myProperties.getProperty(Constants.GameFileFields.FORCE_FILES));

        for (String e : myForceFiles) {
            e = myGameFolder + e;
        }
        
        myCanvas = new Canvas(this, myGameFolder + 
            myProperties.getProperty(Constants.GameFileFields.LEVEL_BACKGROUND));
        myFrame.getContentPane().add(myCanvas);

        myInputManager = new InputManager(myCanvas);
        
        initMenu();

        myFrame.setVisible(true);
    }

    private void startGameLoop () {
        // create a timer to animate the canvas
        myTimer = new Timer(
            Constants.Timing.ONE_SECOND / Constants.Timing.FRAMES_PER_SECOND,
            new ActionListener() {
                @Override
                public void actionPerformed (ActionEvent e) {
                    myLevel.update(
                        Constants.Timing.ONE_SECOND /
                        Constants.Timing.FRAMES_PER_SECOND);
                    myCanvas.repaint();
                    if (myLevel.getLevelWon()) {
                        myTimer.stop();
                        loadNextLevel();
                        startGame();
                    }
                    else if (myLevel.getLevelLost()) {
                        myTimer.stop();
                        reloadLevel();
                        startGame();
                    }
                }
            });
        myTimer.start();
    }

    /**
     * Loads the first level
     */
    public void startGame () {
        clearOutFrame();
        
        if (myLevel == null) {
            loadLevel(0);
        }
        myCanvas.setLevel(myLevel);
        myInputManager.bindHeroAction(myLevel.getHero());
        myFrame.setVisible(true);
        startGameLoop();
    }

    /**
     * Reloads the current level
     */
    public void reloadLevel () {
        loadLevel(myLevelNumber);
    }

    /**
     * Loads the following level
     */
    public void loadNextLevel () {
        loadLevel(myLevelNumber + 1);
    }

    /**
     * Loads the specified level
     * 
     * @param level index of level to be loaded
     */
    public void loadLevel (int level) {
        myLevelNumber = level;
        myLevel = GameFactory.loadLevel(new File(myLevelFiles[level]), myImages);
        
        // TODO: this needs to look at the level to decide!
        myPhysicsManager = GameFactory.loadForces(new File(myForceFiles[0]));
    }
    
    public Dimension getWindowSize () {
        return myFrame.getSize();
    }

    /**
     * Creates a new JFrame and puts a splash screen in it.
     */
    private void initFrameAndSplash () {
        myFrame = new JFrame(
            myProperties.getProperty(Constants.GameFileFields.GAME_NAME));

        SplashScreen splashScreen = new SplashScreen(myGameFolder +
            myProperties.getProperty(Constants.GameFileFields.SPLASH));

        myFrame.setPreferredSize(Constants.GuiProperties.FRAME_SIZE);
        myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        // add our user interface components to Frame and show it
        myFrame.getContentPane().add(splashScreen);
        myFrame.pack();
        myFrame.setVisible(true);
    }

    /**
     * Initializes the game menu and replaces all frame children with the menu.
     */
    private void initMenu () {
        JPanel display = new Menu(myGameFolder +
            myProperties.getProperty(Constants.GameFileFields.MENU), this);
        clearOutFrame();
        myFrame.getContentPane().add(display);
    }

    /**
     * Sleeps until at least (delay) ms has passed since startTime.
     * 
     * @param startTime reference time for the delay (use
     *        System.currentTimeMillis())
     * @param delay amount of time in milliseconds
     */
    private void ensureDelay (long startTime, long delay) {
        long elapsed = System.currentTimeMillis() - startTime;

        try {
            Thread.sleep(Math.max(0, delay - elapsed));
        }
        catch (InterruptedException e) {
            // this should NEVER happen
            e.printStackTrace();
        }
    }

    /**
     * Attempts to turn a String that is formatted like
     *     an array into an actual String array
     * 
     * @param string to convert into an array
     * @return new string sarray
     */
    private String[] parseArray (String string) {
        String[] result;

        // if the argument is of the form  "[**************]" where * is not [ or ]
        if (Pattern.matches("^\\[[^\\[\\]]*+\\]$", string)) {

            String contents = string.substring(1, string.length() - 1);
            result = contents.split(",");
            for (String e : result) {
                e = e.trim();
            }
        }
        else {
            result = new String[1];
            result[0] = string;
        }
        return result;
    }

    private void clearOutFrame () {
        for (Component c : myFrame.getContentPane().getComponents()) {
            try {
                if (!myCanvas.equals((Canvas) c)) {
                    myFrame.getContentPane().remove(c);
                }
            }
            catch (ClassCastException e) {
                myFrame.getContentPane().remove(c);
            }
        }
    }
}
